﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace LPLib
{
    public static class TypeConverter
    {
        static Dictionary<char, int> charToInt = PureNucleotideInt.Encoder;
        static char[] intToChar = SymbolSets.PureNucleotides;
        static char[] byteToChar;
        static Dictionary<char, byte> charToByte = MixedNucleotideByte.Encoder;
        static double aLittleBit = 1.0E-4;
        static double almostOne = 1 - aLittleBit;
        static TypeConverter()
        {
            byteToChar = new char[charToByte.Count];
            foreach (char symbol in SymbolSets.MixedNucleotides)
            {
                byteToChar[charToByte[symbol]] = symbol;
            }
        }

        public static byte ToByte(double[] pmf)
        {
            double sum = pmf.Sum();

            if (sum < almostOne)
                return 15; // the pmf is being used to hold the place for N nucleotides

            List<Tuple<byte, double>> cArray = new List<Tuple<byte, double>>();
            for (byte i = 0; i < 4; i++)
            {
                cArray.Add(new Tuple<byte, double>((byte)Math.Pow(2, i), pmf[i]));
            }
            cArray.Sort((x, y) => y.Item2.CompareTo(x.Item2));
            byte nucByte = 0;

            if (pmf[4] > 0.5)
                return 16;

            double norm = 1 - pmf[4];

            if (cArray[0].Item2 - cArray[1].Item2 > 0.5 * norm)
            {
                nucByte = cArray[0].Item1;
            }
            else if (cArray[0].Item2 + cArray[1].Item2 - 2 * cArray[2].Item2 > 0.5 * norm)
            {
                nucByte = (byte)(cArray[0].Item1 + cArray[1].Item1);
            }
            else if (cArray[3].Item2 > 0.125 * norm)
            {
                nucByte = 15;
            }
            else
            {
                nucByte = (byte)(cArray[0].Item1 + cArray[1].Item1 + cArray[2].Item1);
            }
            return nucByte;
        }

        public static byte[] ToBytes(double[][] pmfs)
        {
            byte[] bytes = new byte[pmfs.Length];
            for (int i = 0; i < pmfs.Length; i++)
            {
                bytes[i] = ToByte(pmfs[i]);
            }
            return bytes;
        }

        public static byte[] ToBytes(string sequence)
        {
            byte[] seq = new byte[sequence.Length];
            for (int i = 0; i < sequence.Length; i++)
            {
                seq[i] = charToByte[sequence[i]];
            }
            return seq;
        }

        public static Polymer<byte> ToPolynucleotideByte(Polymer<double[]> ppmf)
        {
            return new Polymer<byte>(new MixedNucleotideByte(), ppmf.Name, ToBytes(ppmf.Seq), ppmf.Qual);
        }

        public static PolymerCollection<byte> ToPolynucleotideByteCollection(PolymerCollection<double[]> ppmfc)
        {
            PolymerCollection<byte> pbc = new PolymerCollection<byte>(new MixedNucleotideByte());
            pbc.Name = string.Copy(ppmfc.Name);
            foreach (Polymer<double[]> pd in ppmfc)
            {
                Polymer<byte> p = ToPolynucleotideByte(pd);
                pbc.AddMember(p);
            }
            return pbc;
        }

        public static PolymerCollection<double[]> ToPolynucleotidePMFCollection(PolymerCollection<byte> pc)
        {
            PolymerCollection<double[]> ppmf = new PolymerCollection<double[]>(new NucleotidePMF());
            ppmf.Name = string.Copy(pc.Name);
            foreach (Polymer<byte> pb in pc)
            {
                Polymer<double[]> p = ToPolynucleotidePMF(pb);
                ppmf.AddMember(p);
            }
            return ppmf;
        }

        public static double[] ToPMF(int i)
        {
            double[] pmf = new double[SymbolSets.PureNucleotides.Length];
            if (i < 0 || i >= SymbolSets.PureNucleotides.Length)
            {
                return pmf;
            }
            else
            {
                pmf[i] = 1;
            }
            return pmf;
        }

        public static double[][] ToPMFs(int[] seq)
        {
            double[][] pmfs = new double[seq.Length][];
            for (int i = 0; i < seq.Length; i++)
            {
                pmfs[i] = ToPMF(seq[i]);
            }
            return pmfs;
        }

        public static double[] ToPMF(byte b)
        {
            if (b > 16)
            {
                throw new ArgumentException(b.ToString() + " is not a valid nucleotide byte code.");
            }

            double[] p = new double[NucleotidePMF.Dim];
            ByteBits bb = new ByteBits(b);
            for (byte i = 0; i < NucleotidePMF.Dim; i++)
            {
                p[i] = bb[i] ? 1.0 / MixedNucleotideByte.Degeneracy(b) : 0;
            }
            return p;
        }

        public static double[][] ToPMFs(byte[] bytes)
        {
            double[][] pmfs = new double[bytes.Length][];
            for (int i = 0; i < bytes.Length; i++)
            {
                pmfs[i] = ToPMF(bytes[i]);
            }
            return pmfs;
        }

        public static double[] ToPMF(byte b, byte q)
        {
            if (b > 16)
            {
                throw new ArgumentException(b.ToString() + " is not a valid nucleotide byte code.");
            }

            double prob = Math.Pow(10.0, -q / 10.0);

            double[] p = new double[NucleotidePMF.Dim];
            int degen = MixedNucleotideByte.Degeneracy(b);
            if (degen == 4)
            {
                for (byte i = 0; i < 4; i++)
                {
                    p[i] = 0.25;
                }
            }
            else
            {
                ByteBits bb = new ByteBits(b);
                for (byte i = 0; i < 4; i++)
                {
                    p[i] = bb[i] ? ( 1 - prob) / degen : prob/(4-degen);
                }
            }
            return p;
        }

        public static double[][] ToPMFs(byte[] bytes, byte[] qual)
        {
            if (qual != null && bytes.Length != qual.Length)
            {
                throw new ArgumentException("The sequence and quality score arrays are not the same length.");
            }

            double[][] pmfs = new double[bytes.Length][];
            for (int i = 0; i < bytes.Length; i++)
            {
                pmfs[i] = ToPMF(bytes[i], qual[i]);
            }
            return pmfs;
        }

        public static double[][] ToPMFs(string sequence)
        {
            byte[] intermediate = ToBytes(sequence);
            return ToPMFs(intermediate);
        }

        public static Polymer<double[]> ToPolynucleotidePMF(Polymer<byte> p)
        {
            if (p.Qual != null)
                return new Polymer<double[]>(new NucleotidePMF(), p.Name, ToPMFs(p.Seq, p.Qual), p.Qual);
            else
                return new Polymer<double[]>(new NucleotidePMF(), p.Name, ToPMFs(p.Seq)) ;
        }

        public static Polymer<double[]> ToPolynucleotidePMF(Polymer<int> p)
        {
            return new Polymer<double[]>(new NucleotidePMF(), p.Name, ToPMFs(p.Seq), p.Qual);
        }

        public static Polymer<double[]> AsPolynucleotidePMF(this Polymer<byte> p)
        {
            return ToPolynucleotidePMF(p);
        }

        public static Polymer<double[]> AsPolymerPMF(this Polymer<int> p)
        {
            return ToPolynucleotidePMF(p);
        }

        public static char ToChar(int i)
        {
            return intToChar[i];
        }

        public static char ToChar(byte b)
        {
            return byteToChar[b];
        }

        public static char ToChar(double[] p)
        {
            byte intermediate = ToByte(p);
            return ToChar(intermediate);
        }

        public static char[] ToChars(byte[] seq)
        {
            char[] chars = new char[seq.Length];
            for (int i = 0; i < seq.Length; i++)
            {
                chars[i] = byteToChar[seq[i]];
            }
            return chars;
        }

        public static char[] ToChars(double[][] seq)
        {
            byte[] intermediate = ToBytes(seq);
            return ToChars(intermediate);
        }
    }
}
